Resumen ejecutivo
Zoom expone una API REST v2 con base https://api.zoom.us/v2
, autenticada mediante OAuth 2.0 en dos variantes: Server-to-Server OAuth (sin interacción de usuario, ideal para servidores) y OAuth user-managed (consentimiento del usuario y uso de refresh tokens). Además, ofrece Webhooks para recibir eventos (p. ej., meeting.started, recording.completed).
Familias de endpoints frecuentes (no exhaustivo): Reuniones (Meetings), Usuarios, Webinars, Grabaciones en la nube, Chat/Team Chat, Phone, Roles/Cuentas.
Mapa de servicios & ambientes
- Base REST:
https://api.zoom.us/v2
. - Autorización:
- Server-to-Server OAuth: obtiene
access_token
para la cuenta víaPOST https://zoom.us/oauth/token?grant_type=account_credentials&account_id=...
con Authorization: Basic (client_id:client_secret). - OAuth user-managed: flujo authorization code con
/oauth/authorize
→/oauth/token
y refresh tokens.
- Server-to-Server OAuth: obtiene
- Scopes: por ejemplo, para crear reuniones:
meeting:write
omeeting:write:admin
. - Rate limits: por etiqueta (Light/Medium/Heavy) y por endpoint. Verifica cada método antes de escalar.
Requisitos previos
- Cuenta Zoom con acceso al Marketplace (rol Admin/Owner recomendado).
- Crear una app en Marketplace:
- Server-to-Server OAuth (recomendado para backend sin usuarios): copia Account ID, Client ID, Client Secret, y define scopes.
- o OAuth user-managed (si necesitas actuar en nombre de usuarios): configura redirect URI y scopes.
- Webhook Secret Token (si usarás Webhooks) y endpoint público HTTPS.
- Servidor PHP 8.x con extensiones
curl
,openssl
,json
.
Esquema MySQL base
-- Tokens (Server-to-Server, OAuth user-managed)
CREATE TABLE zoom_tokens (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
flow ENUM('S2S','OAUTH_USER') NOT NULL,
access_token VARCHAR(400) NOT NULL,
expires_at DATETIME NOT NULL,
refresh_token VARCHAR(400) NULL, -- sólo para OAuth user-managed
scopes TEXT NULL,
meta JSON NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL
);
-- Reuniones programadas
CREATE TABLE zoom_meetings (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
zoom_meeting_id BIGINT NULL, -- "id" numérico de Zoom
uuid VARCHAR(255) NULL,
host_user VARCHAR(200) NOT NULL, -- email o "me"
topic VARCHAR(255) NOT NULL,
start_time DATETIME NULL,
duration INT NULL,
timezone VARCHAR(64) NULL,
join_url TEXT NULL,
start_url TEXT NULL,
payload JSON NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL
);
-- Webhooks recibidos (auditoría)
CREATE TABLE zoom_webhooks (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
event VARCHAR(120) NOT NULL,
delivery_ts BIGINT NOT NULL, -- unix ts de header (si aplica)
signature VARCHAR(256) NULL, -- x-zm-signature
body MEDIUMTEXT NOT NULL, -- raw body
processed TINYINT(1) DEFAULT 0,
created_at DATETIME NOT NULL
);
Utilitarios PHP
<?php
// Variables de entorno recomendadas:
$ZOOM_ACCOUNT_ID = getenv('ZOOM_ACCOUNT_ID'); // sólo S2S
$ZOOM_CLIENT_ID = getenv('ZOOM_CLIENT_ID');
$ZOOM_CLIENT_SECRET= getenv('ZOOM_CLIENT_SECRET');
$ZOOM_WEBHOOK_SECRET = getenv('ZOOM_WEBHOOK_SECRET'); // para verificación
function http_post_json($url, $headers, $payload){
$ch = curl_init($url);
$h = array_merge(['Content-Type: application/json','Accept: application/json'], $headers);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => $h,
CURLOPT_POSTFIELDS => is_string($payload)? $payload : json_encode($payload, JSON_UNESCAPED_SLASHES),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
]);
$resp = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$err = curl_error($ch);
curl_close($ch);
if ($resp === false) { throw new RuntimeException("cURL error: $err"); }
return [$http, $resp];
}
?>
Autenticación: Server-to-Server OAuth (recomendado)
Obtén un access_token
corto con credenciales de cuenta. No hay refresh token; cuando expire, solicita otro token.
<?php
// 1) Obtener access_token (S2S)
$basic = base64_encode($ZOOM_CLIENT_ID.':'.$ZOOM_CLIENT_SECRET);
$tokenUrl = 'https://zoom.us/oauth/token?grant_type=account_credentials&account_id='.rawurlencode($ZOOM_ACCOUNT_ID);
$ch = curl_init($tokenUrl);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Authorization: Basic '.$basic],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30
]);
$resp = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($resp === false || $http >= 400) { throw new RuntimeException("Zoom token HTTP $http: ".$resp); }
$data = json_decode($resp, true);
$accessToken = $data['access_token'] ?? null;
$expiresIn = $data['expires_in'] ?? 0; // segundos
if (!$accessToken) { throw new RuntimeException('No se obtuvo access_token'); }
// Persistir $accessToken y calcular expires_at = NOW() + $expiresIn
?>
Autenticación: OAuth user-managed (con consentimiento)
Usa cuando necesitas actuar en nombre de usuarios finales. Flujo:
- Redirige a
https://zoom.us/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=URL
. - Intercambia
code
poraccess_token
yrefresh_token
enPOST https://zoom.us/oauth/token
con Authorization: Basic. - Renueva con
grant_type=refresh_token
cuando caduque.
Endpoints clave (ejemplos)
Crear una reunión
POST /users/{userId}/meetings
(puedes usar me
como userId para el propietario del token). Scopes: meeting:write
o meeting:write:admin
. Límite típico del endpoint: 100 solicitudes/día (verifica tu cuenta y etiqueta de rate limit).
<?php
$payload = [
'topic' => 'Demo API',
'type' => 2, // 2=Scheduled
'start_time' => '2025-08-10T17:00:00Z',
'duration' => 30,
'timezone' => 'UTC',
'settings' => [
'host_video' => false,
'participant_video' => false,
'join_before_host' => false,
'mute_upon_entry' => true
]
];
list($http, $resp) = http_post_json(
'https://api.zoom.us/v2/users/me/meetings',
['Authorization: Bearer '.$accessToken],
$payload
);
if ($http !== 201 && $http !== 200) { throw new RuntimeException("Zoom create meeting HTTP $http: $resp"); }
$meeting = json_decode($resp, true);
// Guarda $meeting['id'], $meeting['join_url'], $meeting['start_url'], $meeting['uuid'], etc.
?>
Obtener/actualizar/eliminar
- GET
/meetings/{meetingId}
- PATCH
/meetings/{meetingId}
- DELETE
/meetings/{meetingId}
Listar reuniones del usuario
- GET
/users/me/meetings
(paginado).
Webhooks (suscripción a eventos)
Configura una app en Marketplace → Event Subscriptions, añade eventos (p. ej., meeting.started
, meeting.ended
, recording.completed
) y define el Event notification endpoint URL. Usa el Secret Token para verificar la autenticidad.
Validación por firma (recomendada)
Zoom envía cabeceras como x-zm-request-timestamp
y x-zm-signature
. Calcula el HMAC SHA-256 de la cadena v0:{timestamp}:{raw_body}
usando tu Secret Token y compara con x-zm-signature
.
<?php
// endpoint: /webhooks/zoom
$raw = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_ZM_REQUEST_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_ZM_SIGNATURE'] ?? '';
if (!$ts || !$sig) { http_response_code(400); exit; }
// 1) Prevención de replays (tolerancia 5 min)
if ((time() - (int)$ts) > 300) { http_response_code(403); exit; }
// 2) Verificar firma
$msg = "v0:$ts:$raw";
$calc = hash_hmac('sha256', $msg, $ZOOM_WEBHOOK_SECRET);
$valid = hash_equals($sig, $calc);
if (!$valid) { http_response_code(401); exit; }
// 3) Procesar el evento
$event = json_decode($raw, true);
if (($event['event'] ?? '') === 'endpoint.url_validation') {
// Responder al CRC (challenge-response) si se solicita validación
$plain = $event['payload']['plainToken'] ?? '';
$encrypted = hash_hmac('sha256', $plain, $ZOOM_WEBHOOK_SECRET);
header('Content-Type: application/json');
echo json_encode(['plainToken' => $plain, 'encryptedToken' => $encrypted]);
exit;
}
// ... encolar y procesar meeting.started/ended, recording.completed, etc.
http_response_code(200);
?>
Tips: responde <= 3 s; reintenta idempotente; registra timestamp, firma y raw body. Zoom puede revalidar periódicamente el endpoint.
Paso a paso de implementación
Crear app en Marketplace
- Elige Server-to-Server OAuth (privada) o OAuth (user-managed) según tu caso.
- Define scopes mínimos necesarios (p. ej.,
meeting:write
). - Si usarás Webhooks, copia el Secret Token y configura eventos.
Autenticación
- S2S: implementa solicitud del token con Basic +
account_credentials
; cachea hasta expiración. - OAuth: implementa autorización, refresco de tokens y rotación segura.
Consumo de API
- Crea/actualiza/borra reuniones; guarda
id
,join_url
,start_url
. - Maneja errores y rate limits con backoff exponencial.
Webhooks
- Verifica firma
x-zm-signature
; responde aendpoint.url_validation
si se solicita. - Diseña handlers idempotentes (usa
uuid
del evento para evitar duplicados).
Operación
- Monitorea tasas de respuesta, errores, y latencias por endpoint.
- Registra auditoría con hashes del raw body de webhooks y correlación con reuniones.
Buenas prácticas
- Principio de menor privilegio: solicita sólo los scopes necesarios.
- Rotación de secretos: Client Secret, Webhook Secret Token.
- Cache de tokens y renovación proactiva antes de expirar.
- Backoff + jitter ante 4xx/5xx y límites.
- Validación estricta de firmas y timestamps en Webhooks.
Solución de problemas comunes
Problema | Causa probable | Acción |
---|---|---|
unsupported_grant_type al pedir token S2S | Grant incorrecto o método/verbo erróneo | Usa POST a /oauth/token?grant_type=account_credentials&account_id=... con Authorization: Basic. |
invalid_client | Client ID/Secret o Account ID no coinciden | Verifica credenciales copiadas desde la app Marketplace; reintenta con Basic correcto. |
401 al llamar /v2 | Token expirado o scopes insuficientes | Renueva token; revisa scopes de la app y del endpoint específico. |
Validación de webhook falla | Firma HMAC incorrecta o timestamp fuera de tolerancia | Construye v0:ts:raw_body ; compara con x-zm-signature ; tolerancia ~5 min. |
429 Too Many Requests | Rate limit por etiqueta del endpoint | Implementa backoff; revisa documentación de límites por método. |
Anexos & referencias (útiles para el equipo)
- Base de API y referencias por familia (Meetings, Users, Rooms, Phone, etc.).
- Guías de OAuth (S2S y user-managed).
- Webhooks: verificación de firma y CRC.
- Rate limits por endpoint y etiquetas.
- Nota de deprecación de JWT (migrar a S2S/OAuth).
Publica esta guía internamente; mantén enlaces a la documentación oficial de Zoom y revisa cambios de scopes/límites por versión.